{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "aca37267-1a14-412d-a601-bd7b5eae463d",
   "metadata": {},
   "source": [
    "### 使用聊天模型和提示模板构建一个简单的 LLM 应用程序\n",
    "\n",
    "在本快速入门教程中，我们将向你展示如何使用 LangChain 构建一个简单的大型语言模型（LLM）应用程序。该应用程序将把文本从英文翻译成另一种语言。\n",
    "\n",
    "这是一个相对简单的 LLM 应用程序 —— 它只包含一次 LLM 调用和一些提示词（prompting）操作。尽管如此，这是开始学习 LangChain 的绝佳方式 —— 许多功能只需通过一些提示词和一次 LLM 调用就可以实现！\n",
    "\n",
    "阅读完本教程后，你将对以下内容有一个高层次的了解：\n",
    "\n",
    "- 如何使用语言模型  \n",
    "- 如何使用提示模板（prompt templates）  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "04677a52-a373-45a1-b564-13b6d3e86e77",
   "metadata": {},
   "outputs": [],
   "source": [
    "import getpass\n",
    "import os\n",
    "\n",
    "try:\n",
    "    # load environment variables from .env file (requires `python-dotenv`)\n",
    "    from dotenv import load_dotenv\n",
    "\n",
    "    _ = load_dotenv()\n",
    "except ImportError:\n",
    "    pass"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c1f4543d-0cbb-43c6-a24e-9975e58b56cd",
   "metadata": {},
   "source": [
    "### 使用语言模型\n",
    "\n",
    "首先，我们来学习如何单独使用一个语言模型。LangChain 支持多种不同的语言模型，并且你可以根据需要灵活地切换使用。\n",
    "\n",
    "有关如何开始使用特定模型的详细信息，请参考 LangChain 的[**支持的集成列表**](https://python.langchain.com/docs/integrations/chat/)。\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "36fea8b5-08a1-4a03-8700-62871c8bb4a0",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_community.chat_models.tongyi import ChatTongyi\n",
    "\n",
    "model = ChatTongyi(\n",
    "    streaming=True,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "53dd80f2-e8aa-4a68-9e6d-5186b1919703",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='哈囉！😄', additional_kwargs={}, response_metadata={'model_name': 'qwen-turbo', 'finish_reason': 'stop', 'request_id': '5035d078-03ed-9efa-b3bd-685af1d23184', 'token_usage': {'input_tokens': 25, 'output_tokens': 5, 'total_tokens': 30, 'prompt_tokens_details': {'cached_tokens': 0}}}, id='run--9812a6d4-075f-4174-838c-0ad8286e797b-0')"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain_core.messages import HumanMessage, SystemMessage\n",
    "\n",
    "messages = [\n",
    "    SystemMessage(\"以下内容从英文翻译成地道的粤语\"),\n",
    "    HumanMessage(\"hi!\"),\n",
    "]\n",
    "\n",
    "model.invoke(messages)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "98980fc2-1de5-4a62-861a-60c2a7f1ed94",
   "metadata": {},
   "source": [
    "- 请注意，ChatModels 接收消息对象作为输入，并生成消息对象作为输出。\n",
    "- 除了文本内容外，消息对象还传达对话角色并保存重要数据，例如工具调用次数和代币使用次数。\n",
    "- LangChain 还支持通过字符串或 OpenAI 格式输入聊天模型。以下代码等效："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "37e8258c-2e7c-4916-b9fc-aef19c850ffd",
   "metadata": {},
   "outputs": [],
   "source": [
    "res1 = model.invoke(\"Hello\")\n",
    "\n",
    "res2 = model.invoke([{\"role\": \"user\", \"content\": \"Hello\"}])\n",
    "\n",
    "res3 = model.invoke([HumanMessage(\"Hello\")])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "98f2c8b3-edde-4f15-b654-12a8739471ad",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='Hello! How can I assist you today?', additional_kwargs={}, response_metadata={'model_name': 'qwen-turbo', 'finish_reason': 'stop', 'request_id': 'f9bf7853-e704-940e-a11e-85a80144663e', 'token_usage': {'input_tokens': 9, 'output_tokens': 9, 'total_tokens': 18, 'prompt_tokens_details': {'cached_tokens': 0}}}, id='run--442f970a-f5c0-4586-b474-22b7156bb162-0')"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "res1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "30df06b4-13c2-40bc-80a2-99352303d373",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='Hello! How can I assist you today?', additional_kwargs={}, response_metadata={'model_name': 'qwen-turbo', 'finish_reason': 'stop', 'request_id': '88ef4c29-b6bd-9f40-b3ce-94b3e561aeea', 'token_usage': {'input_tokens': 9, 'output_tokens': 9, 'total_tokens': 18, 'prompt_tokens_details': {'cached_tokens': 0}}}, id='run--6a4845a4-905d-4816-91c3-2a4f56347fdf-0')"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "res2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "7697e79e-c908-48b0-854d-4276190581ea",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='Hello! How can I assist you today?', additional_kwargs={}, response_metadata={'model_name': 'qwen-turbo', 'finish_reason': 'stop', 'request_id': '7e6be60a-9184-9095-8555-2c084cd2c689', 'token_usage': {'input_tokens': 9, 'output_tokens': 9, 'total_tokens': 18, 'prompt_tokens_details': {'cached_tokens': 0}}}, id='run--83df58a3-c9e8-446d-8196-a9ad4be4a982-0')"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "res3"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4147b410-bcb6-4b83-bc1a-f01ba5b25614",
   "metadata": {},
   "source": [
    "**流式传输（Streaming）**  \n",
    "由于聊天模型是可运行对象（Runnables），它们提供了一个标准接口，其中包括异步和流式调用模式。这使我们能够从聊天模型中逐个地流式传输（逐步接收）响应的 token（即生成的文字片段）。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "04237d1a-38e9-451d-8b65-5a09ee4f6618",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "哈|囉|！😊||"
     ]
    }
   ],
   "source": [
    "for token in model.stream(messages):\n",
    "    print(token.content, end=\"|\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "511bac74-0cbb-48e3-8fed-7202c5f4b468",
   "metadata": {},
   "source": [
    "## 提示模板\n",
    "目前，我们是将一组消息直接传递给语言模型。那么，这组消息是从哪里来的呢？  \n",
    "通常，它是由用户输入和应用程序逻辑共同构建而成的。这种应用程序逻辑通常会接收用户的原始输入，并将其转换为一个可用于语言模型的消息列表。  \n",
    "常见的转换操作包括添加系统消息（system message）或将用户输入填入预定义的模板中。\n",
    "\n",
    "**提示模板（Prompt Templates）** 是 LangChain 中的一个概念，专门用于辅助这种转换。  \n",
    "它可以接收用户的原始输入，并返回一个已经格式化好的提示信息（prompt），这个提示可以直接传给语言模型使用。\n",
    "\n",
    "下面我们来创建一个提示模板。该模板将接收两个用户变量：\n",
    "\n",
    "- `language`：要翻译成的目标语言  \n",
    "- `text`：需要翻译的文本"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "96e9d713-4dbc-4ab8-abcd-69005e19cacf",
   "metadata": {},
   "source": [
    "### 系统提示词 - system prompt\n",
    " - system prompt 每次运行都不变,是开发者提前设定好的（但是可以传参）。\n",
    " - 每次都放在user prompt之前。\n",
    " - 并且优先级比user prompt高\n",
    "### 用户提示词 - user prompt\n",
    " - user prompt 用户输入的内容，每次调用都会不一样。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "5e67ee35-16b4-4111-b462-1528fad2c149",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "\n",
    "system_template = \"Translate the following from Chinese Mandarin into {language}\"\n",
    "\n",
    "prompt_template = ChatPromptTemplate.from_messages(\n",
    "    [(\"system\", system_template), (\"user\", \"{text}\")]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ec4158b7-08ca-4f38-a58c-0f8b0fd6435f",
   "metadata": {},
   "source": [
    "请注意，`ChatPromptTemplate` 支持在单个模板中包含多种消息角色（message roles）。  \n",
    "我们将 `language` 参数格式化到系统消息（system message）中，将用户的文本（user text）格式化到用户消息（user message）中。\n",
    "\n",
    "这个提示模板的输入是一个字典。我们可以单独尝试这个提示模板，看看它的输出是什么样子的。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "ffd6c1e2-3a0b-4407-8cd8-c588ba24b4b7",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "ChatPromptValue(messages=[SystemMessage(content='Translate the following from Chinese Mandarin into 粤语', additional_kwargs={}, response_metadata={}), HumanMessage(content='你吃了吗!', additional_kwargs={}, response_metadata={})])"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "prompt = prompt_template.invoke({\"language\": \"粤语\", \"text\": \"你吃了吗!\"})\n",
    "\n",
    "prompt"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cf88806c-cc3e-4fde-b864-f7f5df2418cc",
   "metadata": {},
   "source": [
    "我们可以看到，它返回了一个包含两条消息的 `ChatPromptValue`。如果我们想要直接访问这些消息，可以这样做："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "03d793f8-ee0d-4e70-aeb2-05b3f41b84bf",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[SystemMessage(content='Translate the following from Chinese Mandarin into 粤语', additional_kwargs={}, response_metadata={}),\n",
       " HumanMessage(content='你吃了吗!', additional_kwargs={}, response_metadata={})]"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "prompt.to_messages()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "052440f2-af67-4ee9-b9ef-a6f35a351cbb",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "你食咗未呀！\n"
     ]
    }
   ],
   "source": [
    "response = model.invoke(prompt)\n",
    "print(response.content)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d10a35b7-8bf4-482e-a5ee-a5e52e60dda0",
   "metadata": {},
   "source": [
    "### 总结\n",
    "\n",
    "就这样！在本教程中，你学习了如何构建你的第一个简单的大型语言模型（LLM）应用程序。你了解了如何使用语言模型、如何创建提示模板（prompt template）。\n",
    "\n",
    "这仅仅是你成为一名熟练的 AI 工程师所需知识的冰山一角。幸运的是——我们还有大量其他学习资源！"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
